"""
 @Author: Wxshuai
 @Email: 2444059324@qq.com
 @FileName: utils.py
 @DateTime: 2024/5/7 1:30
 @SoftWare: PyCharm
"""
import json
import os
import sys

import pygame

from game import constants


class Button:
    """
    按钮类的定义，用于创建具有图形界面的按钮。

    参数:
    - text: 按钮上显示的文本内容。
    - pos: 按钮的左上角位置坐标。
    - size: 按钮的尺寸大小。
    - color: 按钮的正常状态颜色。
    - hover_color: 按钮的高亮状态颜色。
    - action: 当按钮被点击时执行的回调函数，默认为None。
    - radius: 按钮的圆角半径，默认为5。
    """

    def __init__(self, text, pos, size, color, hover_color, action=None, radius=constants.BUTTON_RADIUS):
        """
        初始化按钮的各种属性，包括位置、大小、颜色、文本等。
        """
        # 初始化按钮的圆角半径
        self.radius = radius
        # 初始化按钮的文本内容
        self.text = text
        # 初始化按钮的位置
        self.pos = pos
        # 初始化按钮的尺寸
        self.size = size
        # 初始化按钮的正常状态颜色
        self.color = color
        # 初始化按钮的高亮状态颜色
        self.hover_color = hover_color
        # 初始化按钮的回调函数
        self.action = action
        # 初始化按钮的字体
        self.font = pygame.font.Font(constants.FONT_PATH, 36)
        # 初始化按钮的矩形区域
        self.rect = pygame.Rect(pos, size)
        # 初始化按钮是否被鼠标悬停的标志
        self.is_hovered = False

    def draw(self, surface):
        """
        在给定的surface上绘制按钮。

        参数:
        - surface: 绘制按钮的目标表面。
        """
        # 根据按钮是否被悬停来选择颜色
        color = self.hover_color if self.is_hovered else self.color
        # 绘制按钮的矩形形状
        pygame.draw.rect(surface, color, self.rect, border_radius=self.radius)
        # 绘制按钮上的文本
        text_surface = self.font.render(self.text, True, constants.TEXT_COLOR)
        text_rect = text_surface.get_rect(center=self.rect.center)
        surface.blit(text_surface, text_rect)

    def handle_event(self, event):
        """
        处理传入的事件，以响应按钮的状态改变或点击操作。

        参数:
        - event: 需要处理的事件对象。
        """
        # 检查是否有退出游戏的事件
        check_quit_game(event)
        # 处理鼠标移动事件
        if event.type == pygame.MOUSEMOTION:
            self.is_hovered = self.rect.collidepoint(event.pos)
        # 处理鼠标左键点击事件
        elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
            # 如果按钮被悬停且有回调函数，则执行回调函数
            if self.is_hovered and self.action:
                play_sound("click_btn")
                self.is_hovered = False  # 鼠标悬停并触发点击事件时，按钮状态重置
                self.action()


class TextOverlay:
    """
    文本覆盖层类，用于在屏幕上显示提示信息。

    参数:
    screen: pygame屏幕对象，用于显示提示信息。
    help_text: str，要显示的提示文本。
    close_button_position: tuple，关闭按钮的位置坐标。
    """

    def __init__(self, screen, help_text, close_button_position, font, color):
        """
        初始化提示覆盖层。

        参数:
        screen: pygame屏幕对象。
        help_text: 要显示的帮助文本。
        close_button_position: 关闭按钮的坐标。
        """
        self.font = font
        self.screen = screen
        self.is_visible = False  # 控制提示覆盖层是否可见
        self.help_text = help_text
        self.close_button_position = close_button_position
        self.color = color
        # 创建关闭按钮，点击后隐藏提示覆盖层
        self.close_button = Button("✖", close_button_position, (20, 20), constants.BUTTON_COLOR,
                                   constants.HOVER_COLOR, self.hide)

    def show(self):
        """显示提示覆盖层"""
        self.is_visible = True

    def hide(self):
        """隐藏提示覆盖层"""
        self.is_visible = False

    def update(self):
        """
        更新提示覆盖层的状态，如果可见，则在屏幕上绘制覆盖层和关闭按钮。
        """
        if self.is_visible:
            self.screen.blit(self.create_overlay(), (0, 0))  # 绘制覆盖层
            self.close_button.font = self.font
            self.close_button.draw(self.screen)  # 更新关闭按钮状态

    def create_overlay(self):
        """
        创建并返回提示覆盖层的Surface对象。

        返回值:
        pygame.Surface对象，表示提示覆盖层。
        """
        overlay_surface = pygame.Surface((self.screen.get_width(), self.screen.get_height()), pygame.SRCALPHA)
        overlay_surface.fill(constants.OVERLAY_COLOR)  # 创建半透明的棕色覆盖层
        # 定义文本的起始位置
        x_pos, y_pos = constants.HELP_OVERLAY_CONTENT_POS
        lines = wrap_text(self.help_text, self.font, self.color)  # 文本分行
        # 在屏幕上绘制渲染的文本
        for line in lines:
            overlay_surface.blit(line, (x_pos, y_pos))  # 逐行绘制文本
            y_pos += line.get_height()  # 坐标下移以适应下一行

        return overlay_surface

    def draw(self):
        """
        在屏幕上绘制提示覆盖层和关闭按钮（如果覆盖层可见）。
        """
        if self.is_visible:
            self.screen.blit(self.create_overlay(), (0, 0))  # 绘制覆盖层
            self.close_button.draw(self.screen)  # 绘制关闭按钮


class Tetris:
    """
    方块对象，用于表示 Tetris 游戏中的各种方块形状。

    Attributes:
        shape (list): 表示方块形状的0/1二维矩阵。
        x (int): 方块在游戏矩阵中的横向位置。
        y (int): 方块在游戏矩阵中的纵向位置。
    """

    def __init__(self, shape, color, x=0, y=0, block_size=None):
        """
        初始化Tetris类的实例。
        参数:
            shape (list): 方块的形状，用二维列表表示。
            color (str): 方块的颜色。
            x (int, optional): 方块在游戏矩阵中的初始x坐标，默认为0。
            y (int, optional): 方块在游戏矩阵中的初始y坐标，默认为0。
            block_size (int, optional): 方块的尺寸，默认为None，使用全局常量BLOCK_SIZE。
        """
        """
        注意：
            方块的矩阵坐标(x, y)仅代表其在矩阵中的相对位置，并不等同于其在屏幕中的实际绘制坐标，
            但对于游戏区域的方块，可以通过计算推出其绘制坐标：(x ,y) * block_size，
            否则，应当在调用draw方法绘制时临时指定绘制坐标pos
        """
        self.shape = shape
        self.color = color
        self.length = len(shape[0])  # 获取方块的最大长度
        self.block_size = block_size if block_size else constants.BLOCK_SIZE
        # 计算并设置方块的初始x坐标，确保方块位于屏幕中央
        self.x = (constants.GAME_AREA_COL - self.length) // 2
        # 如果传入了x坐标，则使用传入的值，否则使用计算得到的值
        self.x = x if x else self.x
        self.y = y
        # 计算方块的中心位置,用于旋转时计算偏移坐标
        self.center_pos = self.get_center_pos()

    def rotate(self):
        """
        旋转当前方块。

        该方法实现方块的顺时针旋转90度。它首先通过调用rotate_clockwise函数来旋转方块的形状，
        然后更新方块的中心位置，以确保旋转后的方块仍然位于屏幕的中心。

        rotate_clockwise(self.shape): 一个抽象函数，用于实现方块形状的顺时针旋转。
        """
        # 获取旋转后方块的形状矩阵
        shape = rotate_clockwise(self.shape)
        # 计算旋转后方块的新中心位置
        new_center_pos = self.get_center_pos(shape)
        # 计算x方向偏移量
        offset_x = self.center_pos[0] - new_center_pos[0]
        # 计算y方向偏移量
        offset_y = self.center_pos[1] - new_center_pos[1]
        # 检查旋转后的方块是否与游戏矩阵的边缘或已有方块发生碰撞
        if not check_collision(self.x + offset_x, self.y + offset_y, shape):
            # 如果没有发生碰撞，则更新方块的形状矩阵和坐标值(还原偏移量后的坐标)
            self.shape = shape
            self.x += offset_x
            self.y += offset_y
            # 更新方块的中心位置
            self.center_pos = new_center_pos
            # 更新方块的最大长度
            self.length = len(self.shape[0])

    def get_center_pos(self, shape=None):
        """
        获取方块在屏幕坐标系统中的中心位置。

        返回值:
            tuple: 方块的中心位置，格式为(x, y)，其中x和y是相对于屏幕坐标系统的像素位置。
        """
        shape = shape if shape else self.shape
        # 计算中心位置相对于方块左上角的偏移量
        center_x = len(shape[0]) // 2
        center_y = len(shape) // 2

        return center_x, center_y

    def move_left(self):
        """
        将方块向左移动一个单位。
        移动过程中需要检查是否与游戏矩阵的边缘或已有方块发生碰撞。
        """
        # 检查是否与游戏矩阵的边缘或已有方块发生碰撞
        if not check_collision(self.x - 1, self.y, self.shape):
            self.x -= 1

    def move_right(self):
        """
        将方块向右移动一个单位。
        移动过程中需要检查是否与游戏矩阵的边缘或已有方块发生碰撞。
        """
        # 检查是否与游戏矩阵的边缘或已有方块发生碰撞
        if not check_collision(self.x + 1, self.y, self.shape):
            self.x += 1

    def move_down(self):
        """
        将方块向下移动一个单位。
        移动过程中需要检查是否与游戏矩阵的边缘或已有方块发生碰撞。
        """
        # 检查是否与游戏矩阵的边缘或已有方块发生碰撞
        if not check_collision(self.x, self.y + 1, self.shape):
            self.y += 1
            return True
        else:
            return False  # 返回该方块生命周期是否继续

    def draw(self, screen, pos=None, block_size=None, matrix_type=None):
        """
        在给定的屏幕上绘制当前方块。
        参数:
            screen (pygame.Surface): 需要绘制方块的屏幕对象。
            pos (tuple, 可选): 方块的绘制位置，如果未提供，需要根据矩阵坐标计算推出。
            block_size (int, 可选): 方块的尺寸，如果未提供，则为系统默认block_size。
        该方法不返回任何值，它直接在提供的屏幕上指定位置绘制方块。
        """
        """
        注意：
            方块的矩阵坐标(x, y)仅代表其在矩阵中的相对位置，并不等同于其在屏幕中的实际绘制坐标，
            但对于游戏区域的方块，可以通过计算推出其绘制坐标：(x ,y) * block_size，
            否则，应当在调用draw方法绘制时临时指定绘制坐标pos
        """
        # 如果未提供矩阵类别，默认为游戏矩阵绘制
        matrix_type = matrix_type if matrix_type else constants.GAME_MATRIX_TYPE
        # 如果没有提供方块尺寸，则使用方块的默认尺寸
        block_size = block_size if block_size else self.block_size
        # 如果没有提供绘制坐标，则根据方块的矩阵坐标(x, y)计算绘制坐标(x, y) * block_size
        pos = pos if pos else (self.x * block_size, self.y * block_size)
        # 调用draw_matrix函数绘制方块
        draw_matrix(screen, self.shape, pos, self.color, block_size, matrix_type)


def check_collision(x, y, shape):
    """
    检测给定形状是否与游戏区域或已固定的形状发生碰撞。

    参数:
    - x: 形状左上角的x坐标。
    - y: 形状左上角的y坐标。
    - shape: 形状的二维数组，其中1表示有形状块，0表示空白。

    返回:
    - 如果形状与游戏区域或已固定的形状发生碰撞，则返回True；否则返回False。
    """
    # 获取游戏区域矩阵的行和列
    area_row, area_col = constants.GAME_AREA_ROW, constants.GAME_AREA_COL
    # 快速边界检查，先检查是否出界，如果出界则直接返回True
    if not (0 <= x <= area_col - len(shape[0])) or not (0 <= y <= area_row - len(shape)):
        return True
    # 未出界则继续进行细节的碰撞检测，遍历方块矩阵的非空单元格
    for index_row, row in enumerate(shape):
        for index_col, cell in enumerate(row):
            if cell == 0:  # 忽略空白单元格
                continue
            # 计算游戏区域矩阵中的对应位置
            game_area_row = y + index_row
            game_area_col = x + index_col

            # 检查是否超出游戏区域边界或与已固定的形状发生碰撞
            if not (0 <= game_area_col < area_col) or not (0 <= game_area_row < area_row):
                return True
            elif constants.GAME_AREA_MATRIX[game_area_row][game_area_col] == 1:
                return True
    # 如果遍历完所有形状块都没有发生碰撞，则返回False
    return False


def draw_matrix(screen, matrix, pos, color, block_size=None, matrix_type=None):
    """
    绘制方块矩阵

    参数:
    - screen: pygame.Surface对象，表示游戏屏幕。
    - matrix: 0/1二维列表，表示要绘制的矩阵，列表中的每个元素代表一个方块的状态。
    - pos: 元组，表示矩阵左上角在屏幕中的起始坐标。
    - color: 元组，表示方块的颜色。
    - block_size: 可选参数，表示方块的大小，默认为None，使用全局常量BLOCK_SIZE。

    该函数通过遍历矩阵中的每个元素，并根据元素的状态（真或假）绘制相应颜色的方块。
    """
    # 矩阵类型标识, 默认为游戏矩阵
    matrix_type = matrix_type if matrix_type else constants.GAME_MATRIX_TYPE
    # 获取方块的大小，允许自定义单位大小，否则为系统默认大小
    block_size = block_size if block_size else constants.BLOCK_SIZE

    # 遍历矩阵的每一行和每一列
    for index_row, row in enumerate(matrix):
        for index_col, cell in enumerate(row):
            # 计算方块在屏幕中的起始坐标，并创建一个表示方块元素的矩形区域
            rect = pygame.Rect(pos[0] + index_col * block_size, pos[1] + index_row * block_size,
                               block_size, block_size)
            # 如果矩阵中的元素为真（表示该位置有方块），则进行绘制
            if cell == 1:
                # 使用给定的颜色填充方块
                pygame.draw.rect(screen, color, rect)
                # 绘制方块的边框
                pygame.draw.rect(screen, constants.BLOCK_BORDER_COLOR, rect, constants.BLOCK_BORDER)
            # 如果属于预览矩阵，或者游戏矩阵中该位置为空，则使用默认的方块为假的颜色填充方块，防止覆盖
            elif matrix_type == constants.PREVIEW_MATRIX_TYPE or \
                    matrix_type == constants.GAME_MATRIX_TYPE and \
                    constants.GAME_AREA_MATRIX[int(rect.y / block_size)][int(rect.x / block_size)] == 0:
                # 使用默认的方块为假的颜色填充方块
                pygame.draw.rect(screen, constants.BLOCK_FALSE_COLOR, rect)
                # 绘制方块的边框
                pygame.draw.rect(screen, constants.BLOCK_BORDER_COLOR, rect, constants.BLOCK_BORDER)


def rotate_clockwise(matrix):
    """
    顺时针旋转二维矩阵90度。
    参数:
    matrix (List[List[int]]): 需要旋转的二维矩阵。
    返回:
    List[List[int]]: 顺时针旋转90度后的二维矩阵。
    """
    # 先逆置矩阵的行
    reversed_rows = matrix[::-1]
    # 再进行转置操作，得到顺时针旋转的结果
    rotated = list(zip(*reversed_rows))
    # 将zip对象转换为列表
    return [list(row) for row in rotated]


def check_quit_game(event):
    """
    检查是否退出游戏
    """
    # 如果用户点击了关闭按钮，则退出游戏
    if event.type == pygame.QUIT:
        quit_game()


def quit_game():
    """
    退出游戏，退出前更新当前配置数据到配置文件中
    """
    # 退出前保存配置数据文件
    update_config_data()
    try:
        # 游戏退出时停止所有音频，包含背景音乐和音效
        pygame.mixer.music.stop()
        pygame.mixer.stop()
    except pygame.error as e:
        if str(e) == 'mixer not initialized':
            print("Warning: Mixer was not initialized when trying to stop music.")
        else:
            raise e

    # 清理混音器资源（可选）
    pygame.mixer.quit()
    pygame.quit()
    sys.exit()


def fps_handler(screen):
    # 更新FPS配置中的帧率索引，以便实现帧率的循环切换
    constants.FPS_CONFIG["fps_index"] += 1
    # 确保帧率索引不会超出帧率列表的范围，实现循环
    constants.FPS_CONFIG["fps_index"] %= len(constants.FPS_CONFIG["fps_list"])
    # 根据当前的帧率索引，获取对应的帧率值
    constants.FPS = constants.FPS_CONFIG["fps_list"][constants.FPS_CONFIG["fps_index"]]
    # 将获取到的帧率值应用于全局FPS配置，实现帧率的动态调整
    constants.FPS_CONFIG["fps"] = constants.FPS


def music_handler():
    """
    处理音乐开关切换事件。
    """
    constants.GAME_MUSIC = not constants.GAME_MUSIC
    if constants.GAME_MUSIC:
        # 恢复所有音乐音效音量
        constants.GAME_VOLUME = constants.GAME_VOLUME_UNPAUSE
        set_all_audio_volumes(constants.GAME_VOLUME)
    else:
        # 静音所有音乐音效
        constants.GAME_VOLUME = constants.GAME_VOLUME_PAUSE
        set_all_audio_volumes(constants.GAME_VOLUME)


def initialize_audio_resources():
    """初始化音频资源"""
    """开始加载音效资源，根据路径字典动态创建音效对象，方便管理"""
    # 加载所有游戏音效资源，动态创建音效对象字典，方便随时播放
    for key, path in constants.SOUND_PATH.items():
        constants.SOUND_OBJ[key] = pygame.mixer.Sound(path) if path else None
    """然后调整到上次保存的对应音乐开关配置"""
    # 1.先将背景音乐开关设置为与上次保存配置相反状态
    constants.GAME_MUSIC = not constants.GAME_MUSIC
    # 2.然后调用音乐开关切换方法切换回对应状态，目的是触发对应的音乐播放状态的设置
    music_handler()


def play_music(music_name):
    """播放指定背景音乐"""
    # 指定音乐资源路径存在
    if constants.MUSIC_PATHS.get(music_name) is not None:
        # 已有正在播放的音乐资源，则要先停止
        if constants.CURRENT_MUSIC is not None:
            pygame.mixer.music.stop()  # 停止当前音乐
        # 加载新的音乐资源
        pygame.mixer.music.load(constants.MUSIC_PATHS[music_name])
        pygame.mixer.music.play(-1)  # 循环播放新音乐，-1表示无限循环
        constants.CURRENT_MUSIC = music_name  # 更新当前音乐资源名称


def play_sound(sound_name):
    """播放指定游戏音效"""
    # 检查音效名称是否在音效对象字典中，并且对应的音效对象不为空
    if constants.SOUND_OBJ.get(sound_name) is not None:
        constants.SOUND_OBJ[sound_name].play()  # 播放音效对象


def volume_up():
    """音量增大"""
    # 如果当前音量小于最大音量，则增加音量
    if constants.GAME_VOLUME < 1.0:
        constants.GAME_VOLUME += 0.1  # 增加音量
        constants.GAME_MUSIC = True  # 恢复音乐开关状态
        set_all_audio_volumes(constants.GAME_VOLUME)  # 调整音量


def volume_down():
    """音量减小"""
    # 如果当前音量大于最小音量，则减小音量
    if constants.GAME_VOLUME > 0.0 and not float_eq(constants.GAME_VOLUME, 0.0):
        constants.GAME_VOLUME -= 0.1  # 减小音量
        constants.GAME_MUSIC = False if float_eq(constants.GAME_VOLUME, 0.0) else True  # 更新音乐开关状态
        set_all_audio_volumes(constants.GAME_VOLUME)  # 调整音量


def float_eq(a, b, epsilon=1e-9):
    """
    判断两个浮点数是否相等。

    由于浮点数计算的精度问题，直接比较两个浮点数是否相等可能会得到错误的结果。
    因此，这个函数提供了一个小的容差值epsilon，来判断两个浮点数是否“足够接近”。
    如果两个浮点数的差的绝对值小于epsilon，那么认为它们相等。

    参数:
    a -- 第一个浮点数。
    b -- 第二个浮点数。
    epsilon -- 判断浮点数相等的容差值，默认为1e-9。

    返回值:
    如果a和b在epsilon范围内足够接近，则返回True；否则返回False。
    """
    return abs(a - b) < epsilon


def set_all_audio_volumes(volume):
    """
    设置所有音乐和音效的音量，包括正在播放和未来将播放的。

    :param volume: float, 音量值，范围从 0.0 (静音) 到 1.0 (最大音量)
    """
    # 设置背景音乐音量
    pygame.mixer.music.set_volume(volume)

    # 设置所有创建的音效对象的音量
    for sound_obj in constants.SOUND_OBJ.values():
        sound_obj.set_volume(volume)


def theme_handler():
    """
    处理游戏主题切换事件。
    """
    # 从配置数据中获取游戏主题配置
    constants.THEME_CONFIG = constants.CONFIG_DATA.get("theme_config", constants.THEME_CONFIG)
    constants.THEME_LIST = constants.THEME_CONFIG["theme_list"]
    # 根据朱丽列表和当前主题名称切换下一个主题, 并更新配置数据
    index = constants.THEME_LIST.index(constants.GAME_THEME)
    constants.THEME_CONFIG["theme"] = constants.THEME_LIST[(index + 1) % len(constants.THEME_LIST)]
    update_theme_config()


def update_theme_config():
    # 更新游戏主题配置
    constants.THEME_CONFIG = constants.CONFIG_DATA.get("theme_config", constants.THEME_CONFIG)
    constants.THEME_LIST = constants.THEME_CONFIG["theme_list"]
    constants.GAME_THEME = constants.THEME_CONFIG["theme"]
    constants.BLOCK_COLOR_LIST = constants.THEME_CONFIG[constants.GAME_THEME]["block_color_list"]
    constants.BACKGROUND_COLOR = constants.THEME_CONFIG[constants.GAME_THEME]["background_color"]
    # 游戏运行阶段关键元素配色
    constants.GAME_AREA_COLOR = constants.THEME_CONFIG[constants.GAME_THEME]["game_area_color"]
    constants.GAME_RIGHT_COLOR = constants.THEME_CONFIG[constants.GAME_THEME]["game_right_color"]
    constants.BLOCK_COLOR = constants.THEME_CONFIG[constants.GAME_THEME]["block_color"]
    constants.BLOCK_TRUE_COLOR = constants.THEME_CONFIG[constants.GAME_THEME]["block_true_color"]
    constants.BLOCK_FALSE_COLOR = constants.THEME_CONFIG[constants.GAME_THEME]["block_false_color"]
    constants.BLOCK_BORDER_COLOR = constants.THEME_CONFIG[constants.GAME_THEME]["block_border_color"]
    constants.TEXT_COLOR = constants.THEME_CONFIG[constants.GAME_THEME]["text_color"]
    constants.ELSE_TEXT_COLOR = constants.THEME_CONFIG[constants.GAME_THEME]["else_text_color"]
    constants.BUTTON_COLOR = constants.THEME_CONFIG[constants.GAME_THEME]["button_color"]
    constants.HOVER_COLOR = constants.THEME_CONFIG[constants.GAME_THEME]["hover_color"]
    constants.OVERLAY_COLOR = constants.THEME_CONFIG[constants.GAME_THEME]["overlay_color"]


def mode_handler(screen):
    """
    处理游戏模式切换事件。
    """
    # 获取当前游戏模式在游戏模式列表中的索引
    index = constants.GAME_MODE_LIST.index(constants.GAME_MODE)
    # 通过索引加一并取模来实现循环切换游戏模式
    constants.GAME_MODE = constants.GAME_MODE_LIST[(index + 1) % len(constants.GAME_MODE_LIST)]
    # 将当前游戏模式保存到游戏模式配置中，方便保存配置文件时读取
    constants.GAME_MODE_CONFIG["mode"] = constants.GAME_MODE


def pause_handler(screen):
    """
    处理游戏暂停事件。
    """
    constants.GAME_PAUSE = not constants.GAME_PAUSE
    if constants.GAME_PAUSE:
        cancel_custom_timer(constants.USER_EVENT_MOVE_DOWN)
    else:
        set_custom_timer(constants.USER_EVENT_MOVE_DOWN, int(constants.GAME_SPEED * 1000))


def draw_pause_content(screen, pos):
    """
    在游戏暂停时，在屏幕上绘制暂停的提示内容。

    参数:
    - screen: 要绘制文本的屏幕对象。
    - pos: 文本的绘制位置，通常是一个包含x和y坐标的元组。
    - font: 文本的字体对象，用于指定文本的样式和大小。

    该函数不返回任何值，它直接在给定的屏幕上绘制文本。
    """
    # 加载字体
    font = pygame.font.Font(constants.FONT_PATH, constants.PAUSE_TEXT_FONTSIZE)
    # 创建文本表面
    text_surface = font.render(constants.GAME_PAUSE_TEXT, True, constants.WHITE, constants.GRAPHITE_GRAY)
    # 获取文本表面的矩形
    text_rect = text_surface.get_rect()
    # 设置文本位置起始坐标(左上角)
    text_rect.topleft = pos
    # 将暂停提示文本绘制到主屏幕上
    screen.blit(text_surface, text_rect)


def post_event(event_type, event_data=None):
    """
    将一个事件添加到pygame的事件队列中。

    参数:
    event_type -- 事件的类型，应为pygame预定义的事件类型或自定义的事件类型。
    event_data -- 与事件相关的数据字典（可选）。默认为None。

    如果event_data不是None，则会被作为event的属性添加到创建的Event对象中。
    """
    if event_data is None:
        event = pygame.event.Event(event_type)
    else:
        event = pygame.event.Event(event_type, event_data)
    pygame.event.post(event)


def set_custom_timer(event_id, interval):
    """
    设置一个自定义的pygame定时器事件。

    参数:
    event_id (int): 自定义事件的ID。
    interval (int): 定时器触发的时间间隔，单位为毫秒。
    """
    pygame.time.set_timer(event_id, interval)


def cancel_custom_timer(event_id):
    """
    取消一个自定义的定时器事件。

    参数:
    event_id (int): 要取消的自定义事件的ID。
    """
    pygame.time.set_timer(event_id, 0)


def control_fps(clock, frame_rate):
    """
    控制游戏的帧率（FPS）。

    该函数通过调用Pygame中的clock.tick方法来限制游戏的帧率。帧率是指游戏每秒渲染的帧数，
    控制帧率可以避免游戏运行过快，特别是在高性能设备上，同时也可以帮助节省资源。

    参数:
    - clock: Pygame中的Clock对象，用于控制游戏的帧率。
    - frame_rate: 指定的游戏帧率（FPS）。这意味着游戏将尝试每秒渲染frame_rate指定的帧数。

    返回值:
    该函数没有返回值，但它会更新Pygame的Clock对象，以反映上一帧的时间。
    """
    """
    控制游戏帧率。
    """
    clock.tick(frame_rate)


def next_state(state_index):
    """
    根据当前状态返回下一个状态。

    参数:
    state_index -- 当前状态，一个整数，只能是0、1或其它值。

    返回值:
    根据当前状态返回下一个状态，如果当前状态是0，则返回1；如果是1，则返回2；如果是其它值，则返回0。
    """
    if state_index == 0:
        return 1
    elif state_index == 1:
        return 2
    else:
        return 0


# 进入下一个游戏阶段(更新索引)
def go_next_state():
    constants.STATE_INDEX = next_state(constants.STATE_INDEX)  # 更新状态索引


def init_game_state():
    """
    游戏开局前初始化游戏状态，关键参数初始化
    """
    constants.GAME_SCORE = 0
    # 游戏等级，默认为0,仅在挑战模式自动升级生效
    constants.GAME_LEVEL = 0 if constants.GAME_MODE != constants.GAME_CHALLENGE_KEYWORD \
        else constants.GAME_MODE_CONFIG[constants.LEVEL_MIN_KEYWORD]
    constants.GAME_SPEED = constants.GAME_MODE_CONFIG[constants.GAME_MODE]  # 读取对应模式的游戏速度
    constants.GAME_PAUSE = False  # 还原游戏暂停状态
    # 游戏区域矩阵，记录当前游戏状态的方块矩阵，默认为20行10列的全0二维矩阵
    constants.GAME_AREA_MATRIX = [[0 for _ in range(constants.GAME_AREA_COL)]
                                  for _ in range(constants.GAME_AREA_ROW)]
    # 预览区域矩阵，默认为4行13列的全0二维矩阵
    constants.PREVIEW_AREA_MATRIX = [[0 for _ in range(constants.PREVIEW_AREA_COL)]
                                     for _ in range(constants.PREVIEW_AREA_ROW)]


def refresh_game_state():
    """
    刷新游戏状态，重置游戏状态索引和得分
    """
    constants.STATE_INDEX = 0
    constants.GAME_SCORE = 0
    # 游戏等级，默认为0,仅在挑战模式自动升级生效
    constants.GAME_LEVEL = 0 if constants.GAME_MODE != constants.GAME_CHALLENGE_KEYWORD \
        else constants.GAME_MODE_CONFIG[constants.LEVEL_MIN_KEYWORD]
    constants.GAME_SPEED = constants.GAME_MODE_CONFIG[constants.GAME_MODE]  # 读取对应模式的游戏速度
    constants.GAME_PAUSE = False  # 还原游戏暂停状态
    # 游戏区域矩阵，记录当前游戏状态的方块矩阵，默认为20行10列的全0二维矩阵
    constants.GAME_AREA_MATRIX = [[0 for _ in range(constants.GAME_AREA_COL)]
                                  for _ in range(constants.GAME_AREA_ROW)]
    # 预览区域矩阵，默认为4行13列的全0二维矩阵
    constants.PREVIEW_AREA_MATRIX = [[0 for _ in range(constants.PREVIEW_AREA_COL)]
                                     for _ in range(constants.PREVIEW_AREA_ROW)]


def get_absolute_path(relative_file_path):
    """
    获取给定相对路径文件的绝对路径。

    参数:
    relative_file_path - 相对于当前脚本路径的相对文件路径。

    返回值:
    absolute_file_path - 给定相对路径文件的绝对路径。
    """
    script_path = os.path.abspath(__file__)
    # 获取当前脚本的绝对路径，作为计算相对路径的起点
    script_dir = os.path.dirname(script_path)
    # 从当前脚本目录出发，构建目标文件的绝对路径
    absolute_file_path = os.path.join(script_dir, relative_file_path)
    return absolute_file_path


def draw_text(surface, text, position, font, color):
    """
    在给定的Pygame Surface上绘制文本。

    参数:
        surface (pygame.Surface): 要绘制的Pygame Surface。
        text (str): 要显示的文本。
        position (tuple): 文本的左上角坐标，形式为 (x, y)。
        font (pygame.font.Font): Pygame的Font对象。
        color (tuple): 文本颜色，通常为RGB值 (R, G, B)。

    返回:
        None
    """
    """由于原生的绘制文本方法不支持自动换行，所以需要手动实现"""
    # 定义文本的起始位置
    x_pos = position[0]
    y_pos = position[1]
    lines = wrap_text(text, font, color)  # 文本分行处理
    # 在屏幕上绘制渲染的文本
    for line in lines:
        # 逐行绘制文本
        line_rect = line.get_rect()
        line_rect.topleft = (x_pos, y_pos)
        surface.blit(line, line_rect)
        y_pos += line.get_height()  # 坐标下移以适应下一行


def wrap_text(text, font, color):
    """
    将文本渲染为多行Surface列表。

    参数:
    text (str): 需要渲染的文本。
    font: Pygame字体对象，用于渲染文本。
    color: 文本的颜色。

    返回:
    list: 包含每行文本渲染后的Surface对象的列表。
    """
    """
    将文本按照换行符分行。

    参数:
    - text: 要换行的文本字符串。
    - font: Pygame字体对象。

    返回:
    - lines: 包含每行文本的表面列表。
    """
    # 将文本按行分割
    lines = text.split('\n')
    # 对每行文本使用指定字体和颜色进行渲染，并收集到一个列表中
    # 将每一行转换为Surface并收集到列表中
    wrapped_lines = [font.render(line, True, color) for line in lines]

    return wrapped_lines


def read_config_data():
    """
    读取配置文件并更新全局配置数据。

    该函数从指定路径读取JSON格式的配置文件，并将配置数据更新到全局常量中。
    例如：如果配置文件中包含“game_record”项，那么将其值赋给全局常量GAME_RECORD；
        如果配置文件中没有“game_record”项或其值为空，则将GAME_RECORD设为0。

    注意：此函数依赖于全局常量CONFIG_DATA_PATH和CONFIG_DATA，以及一个名为read_json_file的函数，
    这些都应在函数外部定义。
    """
    # 从指定路径读取JSON配置文件，并更新全局配置数据
    constants.CONFIG_DATA = read_json_file(constants.CONFIG_DATA_PATH)

    # 如果配置数据中包含“game_record”，则更新全局变量GAME_RECORD；否则设为0
    constants.GAME_RECORD = constants.CONFIG_DATA.get("game_record", constants.GAME_RECORD)
    # 更新音乐开关状态，默认为True
    constants.GAME_MUSIC = constants.CONFIG_DATA.get("game_music", constants.GAME_MUSIC)
    # 更新FPS配置，默认为60
    constants.FPS_CONFIG = constants.CONFIG_DATA.get("fps_config", constants.FPS_CONFIG)
    constants.FPS = constants.FPS_CONFIG.get("fps", constants.FPS)
    # 更新游戏模式配置
    constants.GAME_MODE_CONFIG = constants.CONFIG_DATA.get("game_mode_config", constants.GAME_MODE_CONFIG)
    constants.GAME_MODE = constants.GAME_MODE_CONFIG["mode"]
    constants.GAME_SPEED = constants.GAME_MODE_CONFIG[constants.GAME_MODE]
    # 游戏等级，默认为0,仅在挑战模式自动升级生效
    constants.GAME_LEVEL = 0 if constants.GAME_MODE != constants.GAME_CHALLENGE_KEYWORD \
        else constants.GAME_MODE_CONFIG[constants.LEVEL_MIN_KEYWORD]
    # 更新游戏主题配置
    update_theme_config()


def update_config_data():
    """
    更新配置数据中的游戏数据，保证游戏数据一致性。
    例如：
    当游戏记录小于游戏得分时，更新游戏记录并写入配置文件。
    这确保了配置数据中的游戏记录总是反映最新的最高得分。
    """
    # 检查是否需要更新游戏记录
    if constants.GAME_RECORD < constants.GAME_SCORE:
        # 更新游戏记录
        constants.GAME_RECORD = constants.GAME_SCORE
        # 更新配置数据中的游戏记录
        constants.CONFIG_DATA["game_record"] = constants.GAME_SCORE
    # 更新音乐开关状态
    constants.CONFIG_DATA["game_music"] = constants.GAME_MUSIC
    # 更新FPS配置
    constants.CONFIG_DATA["fps_config"] = constants.FPS_CONFIG
    # 更新游戏模式配置
    constants.CONFIG_DATA["game_mode_config"] = constants.GAME_MODE_CONFIG
    # 游戏等级，默认为0,仅在挑战模式自动升级生效
    constants.GAME_LEVEL = 0 if constants.GAME_MODE != constants.GAME_CHALLENGE_KEYWORD \
        else constants.GAME_MODE_CONFIG[constants.LEVEL_MIN_KEYWORD]
    # 更新游戏主题配置
    constants.CONFIG_DATA["theme_config"] = constants.THEME_CONFIG
    # 写入更新后的配置数据到JSON文件
    write_json_file(constants.CONFIG_DATA, constants.CONFIG_DATA_PATH)


def read_json_file(file_path):
    """
    从指定的JSON文件读取数据并转换为Python字典。

    参数:
    file_path (str): JSON文件的路径。

    返回:
    dict: 包含JSON文件内容的字典，如果文件不存在或读取失败则返回None。
    """
    try:
        # print(os.getcwd())
        with open(file_path, 'r', encoding='utf-8') as file:
            data = json.load(file)
        return data
    except FileNotFoundError:
        print(f"文件未找到：{file_path}")
        return None
    except json.JSONDecodeError:
        print(f"无法解析JSON文件：{file_path}")
        return None
    except Exception as e:
        print(f"读取文件时发生错误：{e}")
        return None


def write_json_file(dictionary, file_path):
    """
    将Python字典保存为JSON文件。

    参数:
        dictionary (dict): 要保存的字典对象。
        file_path (str): JSON文件的完整路径。

    返回:
        None

    异常:
        IOError: 如果文件无法写入。
        TypeError: 如果字典中的元素不可序列化。
    """
    try:
        with open(file_path, 'w', encoding='utf-8') as json_file:
            json.dump(dictionary, json_file, ensure_ascii=False, indent=4)
    except IOError as e:
        print(f"IOError: {e}")
    except TypeError as e:
        print(f"TypeError: {e}")
